feat: weekly report#177
Conversation
WalkthroughAdds a weekly AI report: new env var and runtime config prompt, a scheduled Nitro task (Fridays 17:30), a new server task that aggregates weekly completed tasks and calls OpenAI to create a Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant CRON as Scheduler (Nitro)
participant Task as ai:weekly-report Task
participant DB as Database
participant AI as OpenAI Client
participant Flow as Flow Repo
CRON->>Task: Trigger (Fri 17:30)
rect rgba(200,230,255,0.35)
Note over Task: Prepare payload
Task->>DB: Task.listCompletedThisWeek()
DB-->>Task: Completed tasks
Task->>DB: Fetch staff/performer data
DB-->>Task: Staff info
end
rect rgba(220,255,220,0.35)
Note over AI: Generate weekly summary
Task->>AI: chat.completions (system: weeklyReportPrompt, user: tasks JSON)
AI-->>Task: finalMessage
end
rect rgba(255,240,200,0.35)
Note over Flow: Persist report
Task->>Flow: Create item (type: weekly_task_report, title with week, description)
Flow-->>Task: OK
end
Task-->>CRON: Success
alt Error path
Task-->>CRON: errorResolver -> success=false
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
apps/web-app/.env.example (1)
27-27: Add brief guidance for the new prompt var.Consider a one‑line comment above with expected format (tone, JSON schema, language) to reduce misconfiguration.
apps/web-app/server/tasks/ai/weekly-report.ts (3)
26-51: Avoid O(N²) counting and repeat lookups; precompute maps.
preparePerformer()filterstaskson every call. BuildstaffByIdandcompletedCountsonce, then reuse.Apply this diff:
- function preparePerformer(performerId: string | null) { - const user = staff.find((user) => user.id === performerId) - if (!user) { - return null - } - - return { - name: user?.name, - surname: user?.surname, - caption: user?.caption, - completedTasksCount: tasks.filter((task) => task.performerId === user?.id).length, - } - } + const staffById = new Map(staff.map(u => [u.id, u])) + const completedCounts = new Map<string, number>() + for (const t of tasks) { + if (!t.performerId) continue + completedCounts.set(t.performerId, (completedCounts.get(t.performerId) ?? 0) + 1) + } + + function preparePerformer(performerId: string | null) { + if (!performerId) return null + const user = staffById.get(performerId) + if (!user) return null + return { + name: user.name, + surname: user.surname, + caption: user.caption, + completedTasksCount: completedCounts.get(user.id) ?? 0, + } + }
71-74: Defensive read of completion result + log empty.Guard against empty choices and add a warning for observability.
Apply this diff:
- const finalMessage = response.choices[0].message.content - if (!finalMessage) { - return { result: true } - } + const finalMessage = response.choices?.[0]?.message?.content?.trim() + if (!finalMessage) { + logger.warn('ai:weekly-report returned empty content') + return { result: true } + }
83-88: Error handling policy: success on failure hides issues.Catching and then returning success can mask outages. Consider returning
{ result: false }or emitting an alert/metric.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web-app/.env.example(1 hunks)apps/web-app/nuxt.config.ts(2 hunks)apps/web-app/server/tasks/ai/daily-report.ts(1 hunks)apps/web-app/server/tasks/ai/weekly-report.ts(1 hunks)packages/database/src/repository/task.ts(1 hunks)packages/database/src/types.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web-app/server/tasks/ai/weekly-report.ts (1)
packages/database/src/tables.ts (1)
tasks(326-338)
packages/database/src/repository/task.ts (1)
packages/database/src/tables.ts (1)
tasks(326-338)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (8)
apps/web-app/server/tasks/ai/daily-report.ts (1)
12-12: Copy edit: description aligns with current behavior.Removing Telegram mention is correct given the task posts to Flow only.
apps/web-app/nuxt.config.ts (2)
22-22: Config surface LGTM.
weeklyReportPromptcorrectly mirrors daily.
85-85: Confirm cron timezone.Nitro uses the server’s TZ. Verify “Friday 17:30” matches your business TZ; otherwise, set container TZ or adjust schedule.
apps/web-app/server/tasks/ai/weekly-report.ts (3)
15-18: Production guard: LGTM.Prevents accidental runs in dev/test.
76-82: Align title’s “week” with DB window.You label by local week number (
w, ru locale) but the query currently uses a rolling 7‑day window. After switching the query todate_trunc('week', now()), consider ISO week (I) to avoid locale surprises, or keepwif that matches business rules—just be consistent.- const week = format(new Date(), 'w', { locale: ru }) + const week = format(new Date(), 'I', { locale: ru }) // ISO week number
23-25: Sanity check — ensure repository.user.findStaff() returns id, name, surname, caption.
Method exists at packages/database/src/repository/user.ts (static async findStaff, ~line 38); confirm it selects or maps id, name, surname and caption because apps/web-app/server/tasks/ai/weekly-report.ts expects those fields.packages/database/src/repository/task.ts (1)
77-82: Use calendar-week bounds (not a rolling 7‑day window) and order by completedAtFile: packages/database/src/repository/task.ts (lines 77-82)
now() - interval '6 day' creates a rolling 7‑day window; switch to calendar-week bounds and sort by completion time.static async listCompletedThisWeek() { - return useDatabase().query.tasks.findMany({ - where: (tasks, { gte }) => gte(tasks.completedAt, sql`now() - interval '6 day'`), - orderBy: (tasks, { desc }) => desc(tasks.updatedAt), - }) + return useDatabase().query.tasks.findMany({ + where: (tasks, { and, gte, lt }) => + and( + gte(tasks.completedAt, sql`date_trunc('week', now())`), + lt(tasks.completedAt, sql`date_trunc('week', now() + interval '7 day')`), + ), + orderBy: (tasks, { desc }) => desc(tasks.completedAt), + }) }If production uses a non‑UTC timezone, confirm the business definition of “week” (we can adapt the SQL with AT TIME ZONE or a specific TZ if required).
packages/database/src/types.ts (1)
39-39: Approve — FlowItemType added; no DB enum/check found.flow_items.type is defined as varchar(...) in packages/database/src/tables.ts:808 and FlowItemType is at packages/database/src/types.ts:39; I found no CREATE TYPE / ALTER TABLE / CHECK enforcing an enum for this column, and weekly_task_report is only used in types + apps/web-app/server/tasks/ai/weekly-report.ts:79 — no DB migration required.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web-app/server/tasks/ai/daily-report.ts (1)
83-88: Surface failures to the scheduler.Same masking pattern as weekly; return success even on error.
Apply:
} catch (error) { errorResolver(error) - } - - return { result: true } + throw error + } + + return { result: true }
🧹 Nitpick comments (7)
apps/web-app/server/tasks/ai/weekly-report.ts (5)
23-25: Skip OpenAI call when there’s nothing to summarize.Avoid paid call and flow write on empty weeks.
Apply:
const tasks = await repository.task.listCompletedThisWeek() const staff = await repository.user.findStaff() + if (tasks.length === 0) { + logger.info('No completed tasks this week; skipping AI call and flow item creation') + return { result: true } + }
26-38: Avoid O(n²) scans per performer; preindex staff and counts.Precompute maps; current filter-per-task per user is quadratic.
Apply within this function:
- function preparePerformer(performerId: string | null) { - const user = staff.find((user) => user.id === performerId) - if (!user) { - return null - } - - return { - name: user?.name, - surname: user?.surname, - caption: user?.caption, - completedTasksCount: tasks.filter((task) => task.performerId === user?.id).length, - } - } + function preparePerformer(performerId: string | null) { + if (!performerId) return null + const user = staffMap.get(performerId) + if (!user) return null + return { + name: user.name, + surname: user.surname, + caption: user.caption, + completedTasksCount: completedCounts.get(performerId) ?? 0, + } + }Add just above this function:
const staffMap = new Map(staff.map(u => [u.id, u])) const completedCounts = tasks.reduce((acc, t) => { if (t.performerId) acc.set(t.performerId, (acc.get(t.performerId) ?? 0) + 1) return acc }, new Map<string, number>())
59-71: Bound cost and increase determinism.Set low temperature and cap tokens; also tag requests with
user.Apply:
const response = await client.chat.completions.create({ model: ai.modelPro, + user: 'ai:weekly-report', + temperature: 0.2, + max_tokens: 800, messages: [If the weekly payload can grow, consider chunking tasks and asking the model to merge summaries.
73-76: Defensive parse of completion and finish reason.Avoid empty/unfinished responses.
Apply:
- const finalMessage = response.choices[0].message.content - if (!finalMessage) { + const choice = response.choices[0] + const finalMessage = choice.message.content?.trim() + if (!finalMessage || choice.finish_reason !== 'stop') { + logger.warn('OpenAI response missing content or not finished: %o', { finish_reason: choice.finish_reason }) return { result: true } }
79-84: Use ISO/padded week for stable titles.
'w'is locale‑week and unpadded. Prefer ISO ('II') or at least padded locale ('ww').Apply one:
- const week = format(new Date(), 'w', { locale: ru }) + const week = format(new Date(), 'II') // ISO week 01–53apps/web-app/server/tasks/ai/daily-report.ts (2)
23-24: Short‑circuit on empty day.Skip OpenAI call when no completed tasks.
Apply:
const tasks = await repository.task.listCompletedToday() + if (tasks.length === 0) { + logger.info('No completed tasks today; skipping AI call and flow item creation') + return { result: true } + }
38-50: Bound cost and increase determinism.Mirror weekly: set
user, lowtemperature, andmax_tokens.Apply:
const response = await client.chat.completions.create({ model: ai.modelPro, + user: 'ai:daily-report', + temperature: 0.2, + max_tokens: 600, messages: [
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web-app/server/tasks/ai/daily-report.ts(2 hunks)apps/web-app/server/tasks/ai/weekly-report.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web-app/server/tasks/ai/weekly-report.ts (1)
packages/database/src/tables.ts (1)
tasks(326-338)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
apps/web-app/server/tasks/ai/weekly-report.ts (2)
52-57: Nice: client hardened with timeout and retries.Matches earlier guidance; reduces hang risk.
15-18: Environment guard: confirm desired behavior for staging.If you run scheduled tasks in staging, consider a runtime flag (
ai.enabled) instead of NODE_ENV check.apps/web-app/server/tasks/ai/daily-report.ts (3)
12-12: Meta description tweak LGTM.
31-36: Good hardening: timeout and limited retries added.Brings parity with weekly task.
46-49: Data sent to OpenAI: confirm policy.Daily payload includes internal task text. Ensure this is approved or gate via config.
| role: 'user', | ||
| content: JSON.stringify(preparedTasks), | ||
| }, |
There was a problem hiding this comment.
PII to third‑party AI: anonymize or gate behind config.
preparedTasks includes names/surnames/captions. Add an opt‑in (e.g., runtimeConfig.ai.allowPII) or hash/initials before sending.
Example guard:
if (!ai.allowPII) {
preparedTasks.forEach(t => {
if (t.performer) {
t.performer = {
...t.performer,
name: undefined,
surname: undefined,
caption: undefined,
}
}
})
}🤖 Prompt for AI Agents
In apps/web-app/server/tasks/ai/weekly-report.ts around lines 67-69, the code
sends preparedTasks (which may contain names, surnames, captions) to a
third-party AI; add a runtime guard or anonymization so PII is not sent by
default. Implement a check like runtimeConfig.ai.allowPII (opt-in) and if false
iterate preparedTasks to remove or replace performer.name, performer.surname and
performer.caption (or replace with initials/hash) before stringifying and
sending to the AI endpoint. Ensure the guard is applied immediately before
building the message payload so no PII is included unless explicitly allowed.
| } catch (error) { | ||
| errorResolver(error) | ||
| } | ||
|
|
||
| return { result: true } |
There was a problem hiding this comment.
Don’t mask failures; propagate to the scheduler.
Currently returns success after errors, hiding incidents.
Apply:
- } catch (error) {
- errorResolver(error)
- }
-
- return { result: true }
+ } catch (error) {
+ errorResolver(error)
+ throw error
+ }
+
+ return { result: true }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (error) { | |
| errorResolver(error) | |
| } | |
| return { result: true } | |
| } catch (error) { | |
| errorResolver(error) | |
| throw error | |
| } | |
| return { result: true } |
🤖 Prompt for AI Agents
In apps/web-app/server/tasks/ai/weekly-report.ts around lines 85 to 89, the
catch block swallows errors and the function always returns success which hides
failures from the scheduler; change the catch to await/errorResolver(error) if
it returns a promise (or call it synchronously if not) and then rethrow the
caught error so the scheduler receives the failure (do not return { result: true
} after an exception).



Summary by CodeRabbit